package ift605.EquationWithAgent.server.agent;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import udes.ds.agent.AbstractEquation;
import udes.ds.agent.BasicEquation;
import udes.ds.agent.Constant;
import udes.ds.agent.Equation;
import udes.ds.agent.MultiplicativeEquation;
import udes.ds.agent.SummativeEquation;

import ift605.EquationWithAgent.server.EquationMessage;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;

public class DerivativeAgent extends Agent {
	private static final long serialVersionUID = 1L;
	protected int mNextRequest;
	protected Map<String,EquationInDerivation> mInProgress;
	
	protected void setup() {
		mNextRequest = 0; // Message ID for a request
		mInProgress = new HashMap<String, EquationInDerivation>(); // DataBase
		addBehaviour(new DerivateBehaviour());
		addBehaviour(new ProcessResultBehaviour());
	}
	
	private class DerivateBehaviour extends CyclicBehaviour{
		private static final long serialVersionUID = 1L;

		@Override
		public void action() {
			ACLMessage request = null;
			MessageTemplate mt = MessageTemplate.MatchConversationId(EquationMessage.DERIVATE_ID);
			request = receive(mt);
			if(request != null){
				try {
					EquationMessage equationRequest = (EquationMessage)request.getContentObject();
					
					// Look at DF to find an agent that derive the equation 
					DFAgentDescription dfd = new DFAgentDescription();
					ServiceDescription sd = new ServiceDescription();
					sd.setType(equationRequest.getEquation().getClass().getSimpleName().toString());
					sd.setName(equationRequest.getEquation().getClass().getSimpleName().toString());
					dfd.addServices(sd);
					DFAgentDescription[] desc = DFService.search(myAgent, dfd); 
					
					ACLMessage response = new ACLMessage(ACLMessage.INFORM);
					
					// If nobody know how to do it, we return a null equation
					if(desc.length == 0){
						response.setConversationId(EquationMessage.RESULT_ID);
						response.addReceiver(request.getSender());
						response.setContentObject(new EquationMessage(equationRequest.getRequestNumber(), null));
						send(response);
					}
					else{
						// Send the equation to the first agent on the list that can derivate the equation
						for(DFAgentDescription agent : desc) {
							response.addReceiver(agent.getName());
							break;
						}
						
						response.setConversationId(EquationMessage.DERIVATE_ID);
						response.setContentObject(new EquationMessage(mNextRequest, equationRequest.getEquation(), 0));
						send(response);
						
						// Add the request in BD to retrieve it's requester
						mInProgress.put(Integer.toString(mNextRequest), new EquationInDerivation( request.getSender(), 
																								  equationRequest.getRequestNumber(), 
																								  equationRequest.getIdentifier()));
						// Increase the message ID for the next request
						mNextRequest += 1;
					}
				} 
				catch (FIPAException e) {
					e.printStackTrace();
				}
				catch (IOException e) {
					e.printStackTrace();
				}
				catch (UnreadableException e) {
					e.printStackTrace();
				}
			}
			else
			{
				block();
			}
		}
	}
	
	private class ProcessResultBehaviour extends CyclicBehaviour{
		private static final long serialVersionUID = 1L;

		@Override
		public void action() {
			ACLMessage request = null;
			MessageTemplate mt = MessageTemplate.MatchConversationId(EquationMessage.RESULT_ID);
			request = receive(mt);
			if(request != null){
				try {
					// Retrive the equation's request in the BD
					EquationMessage equationResult = (EquationMessage)request.getContentObject();
					EquationInDerivation equation = (EquationInDerivation)(mInProgress.get(Integer.toString(equationResult.getRequestNumber())));
					
					// Send the answer of the request to the request's owner
					ACLMessage response = new ACLMessage(ACLMessage.INFORM);
					response.addReceiver(equation.getSender());
					response.setConversationId(EquationMessage.RESULT_ID);
					response.setContentObject(new EquationMessage(equation.getRequestNumber(), cleanEquation(equationResult.getEquation()), equation.getIdentifier()));
					send(response);
					
					// Request is complete, we can remove it from BD
					mInProgress.remove(equationResult);
				}
				catch (IOException e) {
					e.printStackTrace();
				}
				catch (UnreadableException e) {
					e.printStackTrace();
				}
			}
			else
			{
				block();
			}
		}
	}
	
	// Simplify the equation for reasons of readability
	protected AbstractEquation cleanEquation(AbstractEquation aAnswer)
	{
		if( IsZero(aAnswer)) {
			return new Constant(0.0);
		}
		
		AbstractEquation result = aAnswer;
		if(aAnswer.getClass() == Constant.class)
		{}
		else if(aAnswer.getClass() == BasicEquation.class)
		{}
		else if(aAnswer.getClass() == SummativeEquation.class)
		{
			AbstractEquation firstResult = cleanEquation(((SummativeEquation)aAnswer).getFirst());
			AbstractEquation secondResult = cleanEquation(((SummativeEquation)aAnswer).getSecond());
			
			boolean isFirstZero = IsZero(firstResult);
			boolean isSecondZero = IsZero(secondResult);
			
			if(isFirstZero && isSecondZero)
			{
				result = new Constant(0);
			}
			else if(isFirstZero)
			{
				result = secondResult;
			}
			else if(isSecondZero)
			{
				result = firstResult;
			}
			else
			{
				result = new SummativeEquation(firstResult, secondResult);
			}
		}
		else if(aAnswer.getClass() == MultiplicativeEquation.class)
		{

			AbstractEquation firstResult = cleanEquation(((MultiplicativeEquation)aAnswer).getFirst());
			AbstractEquation secondResult = cleanEquation(((MultiplicativeEquation)aAnswer).getSecond());
			
			boolean isFirstZero = IsZero(firstResult);
			boolean isSecondZero = IsZero(secondResult);
			
			if( isFirstZero || isSecondZero)
			{
				result = new Constant(0);
			}
			else
			{
				result = new MultiplicativeEquation(firstResult, secondResult);
			}
		}
		
		return result;
	}
	
	private boolean IsZero(Equation aAnswer)
	{
		if(aAnswer.getClass() == Constant.class)
		{
			return ((Constant)aAnswer).getValue() == 0.0;
		}
		else if(aAnswer.getClass() == BasicEquation.class)
		{
			return ((BasicEquation)aAnswer).getCoefficient() == 0.0;
		}
		else if(aAnswer.getClass() == SummativeEquation.class)
		{
			boolean isFirstZero = IsZero(((SummativeEquation)aAnswer).getFirst());
			boolean isSecondZero = IsZero(((SummativeEquation)aAnswer).getSecond());
			
			if(isFirstZero && isSecondZero)
			{
				return true;
			}
		}
		else if(aAnswer.getClass() == MultiplicativeEquation.class)
		{
			boolean isFirstZero = IsZero(((MultiplicativeEquation)aAnswer).getFirst());
			boolean isSecondZero = IsZero(((MultiplicativeEquation)aAnswer).getSecond());
			
			if(isFirstZero || isSecondZero)
			{
				return true;
			}
		}
		return false;
	}
	
	// Structure of a request
	private class EquationInDerivation{
		private AID mSender;
		private int mRequestNumber;
		private int mIdentifier;
		
		public EquationInDerivation(AID aSender, int aRequestNumber, int aIdentifier){
			mSender = aSender;
			mRequestNumber = aRequestNumber;
			mIdentifier = aIdentifier;
		}
		
		public AID getSender() {
			return mSender;
		}
		
		public int getRequestNumber(){
			return mRequestNumber;
		}
		
		public int getIdentifier(){
			return mIdentifier;
		}
	}
}
